package com.musicdaze.utils.schedule;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;

import com.musicdaze.utils.Pair;

/**
 * Schedules tasks and runs them.
 * @author mfujihara
 *
 */
public class TaskScheduler{
	/** the scheduler */
	private Timer scheduler;
	/** the stored tasks */
	private HashMap<Integer, Pair<Task, List<TaskRunnable>>> tasks;
	/** the date timezone */
	private TimeZone tz;

	/**
	 * constructs a task scheduler
	 */
	public TaskScheduler(){
		scheduler = new Timer();
		tasks = new HashMap<Integer, Pair<Task,List<TaskRunnable>>>();
		tz = TimeZone.getDefault();
	}
	/**
	 * Adds the task to the scheduler
	 * @param name the name of the task
	 * @param t the task to schedule
	 * @return the key for the task for removing
	 */
	public int addTask(Task t){
		List<TaskRunnable> runs = schedule(t);
		int id = getUniqueKey();
		tasks.put(id, new Pair<Task, List<TaskRunnable>>(t,runs));
		return id;
	}
	/**
	 * Removes and cancels a task
	 * @param name the name of the task in this scheduler
	 */
	public void removeTask(int id){
		Pair<Task,List<TaskRunnable>> p = tasks.get(id);
		if (p !=null){
			p.left.end();
			for (TaskRunnable r : p.right){
				r.cancel();
			}
			scheduler.purge();
		}
	}
	/**
	 * Schedules the task
	 * @param t the task
	 * @return the list of runnables added
	 */
	private List<TaskRunnable> schedule(final Task t) {
		Schedule s = t.getSchedule();
		List<TaskRunnable> runs = new ArrayList<TaskRunnable>();
		switch((int)s.getPeriod()){
		case 0:
			// no period
			TaskRunnable run = new TaskRunnable(t);
			runs.add(run);
			scheduler.schedule(run,s.getDate());
			break;
		case -1:
			// weekly
			Calendar c = new GregorianCalendar(tz);
			c.setTime(s.getDate());
			boolean[] days = s.getDaysRepeat();
			int[] dow = new int[]{
					Calendar.SUNDAY,
					Calendar.MONDAY,
					Calendar.TUESDAY,
					Calendar.WEDNESDAY,
					Calendar.THURSDAY,
					Calendar.FRIDAY,
					Calendar.SATURDAY};
			for (int i=0;i<days.length;i++){
				if (days[i]){
					c.set(Calendar.DAY_OF_WEEK, dow[i]);
					Date d = c.getTime();
					run = new TaskRunnable(t);
					if (d.before(s.getDate())){
						c.add(Calendar.DAY_OF_MONTH, 7);
						d = c.getTime();
					}
					runs.add(run);
					scheduler.scheduleAtFixedRate(run, s.getDate(), s.getWeeks()*7*24*60*60*1000);
				}
			}
			break;
		default:
			// random period
			long p = s.getPeriod();
			run = new TaskRunnable(t);
			runs.add(run);
			scheduler.scheduleAtFixedRate(run, s.getDate(), p);
			break;
		}
		return runs;
	}
	/**
	 * Gets a unique key for the tasks to use in the
	 * map
	 * @return a unique key
	 */
	private int getUniqueKey(){
		int key = tasks.size();
		while (tasks.containsKey(key)){
			key++;
		}
		return key;
	}
	/**
	 * Wraps a Task in a TimerTask
	 * @author mfujihara
	 *
	 */
	private static class TaskRunnable extends TimerTask{
		/** the task */
		Task t;
		/** Creates a TaskRunnable with
		 * given task
		 * @param t the task
		 */
		public TaskRunnable(Task t){
			this.t = t;
		}
		/** {@inheritDoc} */
		@Override
		public void run() {
			t.commence();
		}
	}
}
